﻿package MineSweeper;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.*;

import Chapter06.SimpleBomb;

// 格子的狀態
enum CellState {
	COVERED,	// 未開啟
	OPENED,		// 已開啟
	FLAGED,		// 設成旗子
	QUESTION	// 設成問號
};

public class MineSweepGame implements MouseListener {
	int width;		// 棋盤寬度
	int height;		// 棋盤高度
	int size;		// 棋盤大小
	int numOfBombs;	// 炸彈數目

	SimpleBomb bombGame = new SimpleBomb();	// 用來處理遊戲邏輯的物件
	
	HashMap<Integer, Color> colorTable;		// 顏色表，格子開啟後不同的數字用不同顏色
	
	ArrayList<JButton> buttons;				// 用來存放每一格的按鈕物件
	ArrayList<CellState> statesOfCells;		// 用來存放每一格的狀態
	
	ArrayList<Integer> readyToOpenIndexList;	// 用來存放連續空格時要打開的清單

	Font originFont;	// 原始字型

	boolean isFirstHand = true;	// 是否為第一手
	
	JFrame frame = new JFrame();
	JPanel panel = new JPanel();
    JPanel menuPanel = new JPanel();
    
    // Panel A
    JTextField textWidth = new JTextField(2);
    JTextField textHeight = new JTextField(2);
    JTextField textBombs = new JTextField(2);
    
    // Panel B
	JLabel timerLabel = new JLabel("00:00");	// 時間文字
	JButton resetButton = new JButton("^_^");	// 重設按鈕
	JCheckBox checkBox = new JCheckBox("動畫演出");
	
	Integer passedSeconds = 0;
	Timer timer;
	
	// 建構式
	public MineSweepGame() {
		colorTable = new HashMap<Integer, Color>();
		colorTable.put(0, Color.BLACK);
		colorTable.put(1, Color.BLUE);
		colorTable.put(2, Color.GREEN);
		colorTable.put(3, Color.RED);
		colorTable.put(4, Color.CYAN);
		colorTable.put(5, Color.ORANGE);
		colorTable.put(6, Color.PINK);
		colorTable.put(7, Color.MAGENTA);
		colorTable.put(8, Color.GRAY);		

		buttons = new ArrayList<JButton>();
		statesOfCells = new ArrayList<CellState>();
		readyToOpenIndexList = new ArrayList<Integer>();
	}

	public void run() {
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
		// 設定上方選單列的 UI
		JPanel panelA = new JPanel();
		//panelA.setBackground(Color.BLUE);
		panelA.add(new JLabel("寬"));
		panelA.add(textWidth);
		panelA.add(new JLabel("高"));
		panelA.add(textHeight);
		panelA.add(new JLabel("地雷"));
		panelA.add(textBombs);
		
		JPanel panelB = new JPanel();
		//panelB.setBackground(Color.GREEN);
		panelB.add(timerLabel);
        resetButton.addMouseListener(new ResetButtonListener());
        panelB.add(resetButton);
        panelB.add(checkBox);
        
        menuPanel.setLayout(new BoxLayout(menuPanel, BoxLayout.Y_AXIS));
        menuPanel.add(panelA);
        menuPanel.add(panelB);
		
		frame.setLayout(new BorderLayout());
		frame.add(menuPanel, BorderLayout.NORTH);
		frame.add(panel, BorderLayout.CENTER);
		frame.setVisible(true);

		restartGame(8,8,11);
	}

	// TODO 重新啟動遊戲需要重設 Timer
	void restartGame() {
		isFirstHand = true;
		
		bombGame.setupTable(width, height, numOfBombs);
		
		for (int i = 0; i < size; ++i) {
			JButton btn = buttons.get(i);
			btn.setText("");
			btn.setIcon(null);
			btn.setBackground(Color.WHITE);
			statesOfCells.set(i, CellState.COVERED);
		}
		
		resetButton.setText("^_^");
	}
	
	// TODO 重新啟動遊戲需要重設 Timer
	void restartGame(int w, int h, int numBombs) {
		width = w;
		height = h;
		size = width * height;
		numOfBombs = numBombs;
		
		isFirstHand = true;
		
		// 清除既有資料
		buttons.clear();
		statesOfCells.clear();
		panel.removeAll();
		
		bombGame.setupTable(width, height, numOfBombs);
		panel.setLayout(new GridLayout(height, width));
		
		for (int i = 0; i < size; ++i) {
			JButton btn = new JButton("");
			panel.add(btn);
			buttons.add(btn);
			statesOfCells.add(CellState.COVERED);
			
			btn.setMargin(new Insets(2, 2, 2, 2));
			btn.setBackground(Color.WHITE);
			btn.addMouseListener(this);	// 註冊事件通知
		}
		
		resetButton.setText("^_^");
		
		originFont = buttons.get(0).getFont();
		
		Insets i = frame.getInsets();
		frame.setSize(30 * width + i.left + i.right, 
				      30 * height + i.top + i.bottom + menuPanel.getSize().height);
		
		setFrameToCenter();
	}
	
	private void setFrameToCenter() {
		// 將視窗置中
		Dimension screenSize = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
		Dimension frameSize = frame.getSize();
		
		frame.setLocation((screenSize.width-frameSize.width)/2,
				          (screenSize.height-frameSize.height)/2);
		frame.repaint();
	}
	
	@Override
	public void mouseClicked(MouseEvent e) {
		// 滑鼠按鍵按下並且放開
	}

	@Override
	public void mouseEntered(MouseEvent e) {
		// 滑鼠游標進入某個視窗元件範圍
		// Bonus: 隱藏秘技
		JButton b = (JButton) (e.getSource());
		Integer cellIndex = buttons.indexOf(b);
		if (statesOfCells.get(cellIndex) == CellState.COVERED && bombGame.isBombAt(cellIndex)) {
			frame.setTitle(".");
		}
	}

	@Override
	public void mouseExited(MouseEvent e) {
		// 滑鼠游標離開某個視窗元件範圍
		// Bonus: 隱藏秘技
		frame.setTitle("");
	}

	@Override
	public void mousePressed(MouseEvent e) {
		// 滑鼠按鍵按下
		JButton b = (JButton) (e.getSource());
		Integer cellIndex = buttons.indexOf(b);
		if (e.getButton() == MouseEvent.BUTTON1) { // BUTTON1: 按下滑鼠左鍵
			// 如果該格已經開過，或是標記為旗子，就不動作
			CellState state = statesOfCells.get(cellIndex);
			if (state == CellState.OPENED || state == CellState.FLAGED) {
				return;
			}
			// 先檢查是否為第一次開格子
			if (isFirstHand) {
				checkFirstHand(cellIndex);
				bombGame.printTable(); // 顯示解答在下面視窗
				isFirstHand = false;
			}
			// 檢查是地雷或是一般格子
			Integer result = bombGame.getNumOfNearBombs(cellIndex);
			if (result == -1) { // 如果是炸彈
				openBomb(b, cellIndex);
			} else { // 如果不是炸彈
				calcAndOpenCells(cellIndex);		// 計算並打開格子
			}
		} else if (e.getButton() == MouseEvent.BUTTON3) { // BUTTON3: 按下滑鼠右鍵
			CellState cellState = statesOfCells.get(cellIndex);
			if (cellState == CellState.OPENED) {
				return;
			} else {
				markCell(b, cellIndex, cellState);
			}
		}
	}

	// 檢查第一次打開的是不是地雷，如果是地雷，就交換地雷位置
	private void checkFirstHand(int cellIndex) {
		// TODO 把周圍八格也設成非地雷
		if (bombGame.isBombAt(cellIndex)) {
			//bombGame.removeBombAt(cellIndex);	// 1a. 移除該格地雷
			bombGame.swapBombAt(cellIndex);		// 1b. 交換地雷位置
			bombGame.calculateAllCell();		// 2. 重新計算表格
		}

		// 開始計時
		timer = new Timer();
        timer.schedule(new CountDownTask(), 1000, 1000);
	}
	
	// 檢查是否遊戲勝利 - 以旗子標示為主要判斷
	private boolean checkVictoryByRight() {
		Integer cellIndex;
		for (int i = 0; i < width; i++) {
			for (int j = 0; j < height; j++) {
				cellIndex = i * width + j;
				if ( 
				((statesOfCells.get(cellIndex) == CellState.FLAGED) && !bombGame.isBombAt(cellIndex)) ||
				((statesOfCells.get(cellIndex) != CellState.FLAGED) && bombGame.isBombAt(cellIndex))
				) {
					return false;
				}
			}
		}
		return true;
	}
	
	// 檢查是否遊戲勝利 - 以剩餘空格為主要判斷
	public boolean checkVictoryByLeft() {
		Integer cellIndex;
		for (int i = 0; i < width; i++) {
			for (int j = 0; j < height; j++) {
				cellIndex = i * width + j;
				if (statesOfCells.get(cellIndex) == CellState.COVERED &&
					!bombGame.isBombAt(cellIndex)) {
					return false;
				}
			}
		}
		return true;
	}

	// 計算要翻開哪些格子，並將其翻開
	public void calcAndOpenCells(Integer cellIndex) {
		// 清空上次計算結果，以便重新計算
		readyToOpenIndexList.clear();

		// 找出所有符合條件的格子並放在"格子序號ArrayList"
		findNeighborZeroes(cellIndex);

		// 把開格子的工作丟到另外一個 Thread 去處理
        Thread myThread = new Thread(new OpenCellsInList());
        myThread.start();
	}

	// 尋找連續的空白區塊，並把要打開的格子放到 readyToOpenIndexList 中
	public void findNeighborZeroes(Integer cellIndex) {
		// 如果 cellIndex 格子裡放的不是地雷，就先把 cellIndex 加入要打開的清單中
		Integer numOfNearBombs = bombGame.getNumOfNearBombs(cellIndex);
		if (numOfNearBombs != -1) {
			readyToOpenIndexList.add(cellIndex);
		}

		// 以 cellIndex 為中心，對九宮格作遞迴式搜尋
		if (numOfNearBombs == 0) {
			boolean isLeftMost = (cellIndex % width == 0);
			boolean isRightMost = (cellIndex % width == width - 1);
			boolean isTopMost = (cellIndex < width);
			boolean isBottomMost = (cellIndex >= width * (height - 1));

			Integer indexToCheck;
			for (int i = -1; i <= 1; i++) { // i: 由上到下
				// 最上橫排的情況，往上不需要
				if (isTopMost && i == -1)	{ continue; }
				// 最下橫排的情況，往下不需要
				if (isBottomMost && i == 1) { continue; }
				for (int j = -1; j <= 1; j++) { // j: 由左到右
					// 檢查格本身，不需要搜尋
					if (i == 0 && j == 0) {	continue; }
					// 最左直排的情況，往左不需要
					if (isLeftMost && j == -1) { continue; }
					// 最右直排的情況，往右不需要
					if (isRightMost && j == 1) { continue; }
					indexToCheck = cellIndex + i * width + j;
					if (readyToOpenIndexList.contains(indexToCheck)) {
						continue; // 已經包含在清單裡，不需要繼續搜尋
					}
					// 通過上面邊界檢查後，才可以繼續搜尋
					findNeighborZeroes(indexToCheck);
				}
			}
		}
	}
	
	// 左鍵翻到地雷
	private void openBomb(JButton b, Integer cellIndex) {
		openOneBomb(b, cellIndex);
		timer.cancel();
		resetButton.setText(">_<");
		Integer answer = JOptionPane.showConfirmDialog(null, 
				"喔喔～踩到地雷了~\n是否重新開局？", "title",
				JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.INFORMATION_MESSAGE);
		if (answer == JOptionPane.YES_OPTION) {
			// 重新開局
			restartGame();
		} else if (answer == JOptionPane.NO_OPTION) {
			// 返回上一步（把按鈕設回原狀）
			b.setIcon(null);	// 取消按鈕圖案
			statesOfCells.set(cellIndex, CellState.COVERED);
			timer = new Timer();
			timer.schedule(new CountDownTask(), 1000, 1000);
		} else if (answer == JOptionPane.CANCEL_OPTION) {
			// 顯示所有地雷
			ArrayList<Integer> bombList = bombGame.getBombs();
			for (Integer bombIndex : bombList) {
				JButton btn = buttons.get(bombIndex);
				openOneBomb(btn, bombIndex);
			}
			resetButton.setText("T_T");
		}
	}

	// 翻開單一地雷
	public void openOneBomb(JButton b, Integer cellIndex) {
		// 設定按鈕圖案
		ImageIcon icon = createImageIcon("img/bomb.png",
                "a pretty but meaningless splat");
		b.setIcon(icon);
		statesOfCells.set(cellIndex, CellState.OPENED);
	}
	
	// 左鍵翻開一個格子
	public void openCell(JButton b, Integer cellIndex) {
		Integer numOfNearBombs = bombGame.getNumOfNearBombs(cellIndex);
		
		// 如果是 0, 按鈕文字為空；如果非 0, 按鈕文字為周邊地雷數量
		String buttonText = 
				(numOfNearBombs == 0) ? "" : "" + numOfNearBombs;
		b.setText(buttonText);
		
		b.setForeground(colorTable.get(numOfNearBombs));
		b.setBackground(Color.GRAY);
		statesOfCells.set(cellIndex, CellState.OPENED);
	}
	
	// 右鍵標記一個格子
	private void markCell(JButton b, Integer cellIndex, CellState cellState) {
		// TODO 用 icon 來設定圖案
		if (cellState == CellState.COVERED) { // 尚未標記旗子 -> 標記旗子
			b.setIcon(createImageIcon("img/flag.png", "flag icon"));
			statesOfCells.set(cellIndex, CellState.FLAGED);
			// 檢查是否遊戲勝利
			if (checkVictoryByRight()) {
				Integer answer = JOptionPane.showConfirmDialog(null, 
						"恭喜你獲勝了~\n是否再玩一局？", "標旗勝利", 
						JOptionPane.INFORMATION_MESSAGE);
				if (answer == JOptionPane.YES_OPTION) {
					restartGame();
				}
			}
		} else if (cellState == CellState.FLAGED) { // 已標記旗子 -> 改標記問號
			b.setIcon(null);
			b.setText("?");
			b.setFont(originFont);
			b.setForeground(Color.BLACK);
			statesOfCells.set(cellIndex, CellState.QUESTION);
		} else if (cellState == CellState.QUESTION) { // 已標記問號 -> 清空標記
			b.setText("");
			b.setFont(originFont);
			b.setForeground(Color.BLACK);
			statesOfCells.set(cellIndex, CellState.COVERED);
		}
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		// 滑鼠按鍵放開
	}
	
	class ResetButtonListener implements MouseListener {

		@Override
		public void mouseClicked(MouseEvent e) {
			try {
				Integer w = Integer.valueOf(textWidth.getText());
				Integer h = Integer.valueOf(textHeight.getText());
				Integer b = Integer.valueOf(textBombs.getText());
				
				if ((5 <= w && w <= 20) && 
					(5 <= h && h <= 20) &&
					(6 <= b && b <= (w-2)*(h-2))) {
					restartGame(w, h, b);
				} else {
					restartGame();
				}
			} catch (NumberFormatException e1) {
				//e1.printStackTrace();
				restartGame();
			}
		}

		@Override
		public void mouseEntered(MouseEvent e) {
			
		}

		@Override
		public void mouseExited(MouseEvent e) {
			
		}

		@Override
		public void mousePressed(MouseEvent e) {
			resetButton.setText("^o^");
		}

		@Override
		public void mouseReleased(MouseEvent e) {
			resetButton.setText("^_^");
		}
	} // end of class ResetButtonListener
	
	// 打開連續空格的功能，
	// 寫成 thread 是為了能夠 Sleep 一段時間後處理，以做到慢速開格子的動畫
	class OpenCellsInList implements Runnable {

		@Override
		public void run() {
			for (Integer indexOfZeroCell : readyToOpenIndexList) {
				if (statesOfCells.get(indexOfZeroCell) == CellState.FLAGED) {
					continue;
				}
				JButton b = buttons.get(indexOfZeroCell);
				
				if (checkBox.isSelected()) {
					b.setBackground(Color.YELLOW);
					try {
						Thread.sleep(300);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}				
				}
				
				openCell(b, indexOfZeroCell);
			}
			// 檢查是否遊戲勝利
			if (checkVictoryByLeft()) {
				Integer answer = JOptionPane.showConfirmDialog(null, 
						"恭喜你獲勝了~\n是否再玩一局？", "翻牌勝利", 
						JOptionPane.INFORMATION_MESSAGE);
				if (answer == JOptionPane.YES_OPTION) {
					restartGame();
				}
			}
		}
	} // end of class OpenCellsInList
	
	class CountDownTask extends TimerTask {

		// TODO 獲勝時的 timer 控制
		
		@Override
		public void run() {
			passedSeconds++;
			int min = passedSeconds / 60;
			int sec = passedSeconds % 60;
			timerLabel.setText(String.format("%02d:%02d", min, sec));
		}
		
	}
	
	/** Returns an ImageIcon, or null if the path was invalid. */
	protected ImageIcon createImageIcon(String path,
	                                           String description) {
		// 使用 getClass() 拿到 .class 的路徑
//		java.net.URL imgURL = getClass().getResource(path);
//	    if (imgURL != null) {
//	        return new ImageIcon(imgURL, description);
//	    } else {
//	        System.err.println("Couldn't find file: " + path);
//	        return null;
//	    }
	    
	    // 使用 user.dir 拿到目前程式執行的路徑
	    String dir = System.getProperty("user.dir");
	    String imgPath = dir + "/" + path;
	    return new ImageIcon(imgPath, description);
	}
}
